package com.mfs.manager.service;

import com.mfs.common.enums.ResponseCodeEnum;
import com.mfs.common.response.ResponseUtil;
import com.mfs.common.utills.IdUtil;
import com.mfs.manager.log.Log;
import com.mfs.manager.mapper.PortUsedMapper;
import com.mfs.manager.mapper.ServerMapper;
import com.mfs.manager.mapper.dbobj.PortUsed;
import com.mfs.manager.mapper.dbobj.Server;
import com.mfs.manager.service.dto.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

@Service
public class ProxyService {
    private final static Logger logger = LoggerFactory.getLogger(ProxyService.class);
    @Autowired
    private ServerMapper serverMapper;

    @Autowired
    private PortUsedMapper portUsedMapper;

    /**
     * 为代理客户端分配代理服务器
     * 收到分配请求后，首先查询是否可以为用户分配与tartPort一致的代理端口
     * 如果能够分配到tartPort端口，则直接在端口占用表插入记录
     * 否则，为用户随机分配一个可以空闲的代理端口
     * @param request targetIp 表示客户但请求被代理的ip，非空p
     *                targetPort 表示客户端请求被代理的端口， 非空
     *                userCode 表示当前登录的用户编号，非空
     * @return ProxyMallocResponse outIp 分配的代理服务器的外网ip，客户端连接服务端时就使用该ip，建立好代理也使用该ip请求代理端口
     *                             ip 分配的代理服务器的内网ip，云环境下外网ip并不是直接绑定到服务器的，而是和其中的内网ip做了一个映射，该ip就是映射的内网ip
     *                             port 分配的代理端口
     * */
    @Transactional
    @Log
    public ProxyMallocResponse malloc(ProxyMallocRequest request) {
        ProxyMallocResponse response = new ProxyMallocResponse();


        // 尝试分配与targetPort相同的代理端口
        Server server = serverMapper.queryAUsableServerWithLock(request.getTargetPort());
        int proxyPort;
        if (server != null) {
            proxyPort = request.getTargetPort();
        } else {
            // todo 下一步支持分配随机端口
            response.setErrorNo(ResponseCodeEnum.ERROR.getCode());
            response.setErrorInfo(request.getTargetPort()  + "端口繁忙，暂不支持分配随机端口");
            return response;
        }
        PortUsed portUsed = PortUsed.builder()
                .id(IdUtil.snowId())
                .serverId(server.getId())
                .port(proxyPort)
                .userCode(request.getUserCode())
                .targetIp(request.getTargetIp())
                .targetPort(request.getTargetPort())
                .channelNum(0)
                .build();
        int insert = portUsedMapper.insert(portUsed);
        if (insert != 1) {
            logger.error("插入端口占用表失败");
            response.setErrorNo(ResponseCodeEnum.ERROR.getCode());
            response.setErrorInfo("分配代理服务器失败");
            return response;
        }
        response.setIp(server.getIp());
        response.setPort(proxyPort);
        response.setOutIp(server.getOutIp());
        response.setErrorNo(ResponseCodeEnum.SUCCESS.getCode());
        return response;
    }
    
    /**
     * 验证代理代理信息是否正确，防止客户端没有经过分配就伪造代理信息与代理服务端握手
     * userCode、targetIp、targetPort、outIp、ip、proxyPort必须与端口分配表中分配的信息一致
     * 
     * @param request
     * @return VerifyMallocResponse
     * */
    @Transactional
    @Log
    public VerifyMallocResponse verifyMalloc(VerifyMallocRequest request) {
        PortUsed portUsed = portUsedMapper.queryPortUsedInfoByServerInfoAndPortForUpdate(request.getOutIp(),
                request.getIp(), request.getProxyPort());
        if (portUsed == null || !Objects.equals(request.getUserCode(), portUsed.getUserCode())
                || !Objects.equals(request.getTargetIp(), portUsed.getTargetIp())
                || !Objects.equals(request.getTargetPort(), portUsed.getTargetPort())
                || portUsed.getChannelNum() >= 4) {
            return ResponseUtil.createResponse(ResponseCodeEnum.ERROR, VerifyMallocResponse.class);
        }
        // 如果验证通过则，新增一条客户端通道
        portUsedMapper.updateChannelNumById(portUsed.getId(), portUsed.getChannelNum() + 1);
        return ResponseUtil.createResponse(ResponseCodeEnum.SUCCESS, VerifyMallocResponse.class);
    }

    /**
     * 代理服务端回调接口，减少一条客户端通道
     * 如果当前通道是最后一条则同时关闭该代理分配
     */
    @Transactional
    @Log
    public CloseClientChannelResponse closeClientChannel(CloseClientChannelRequest request) {
        PortUsed portUsed = portUsedMapper.queryPortUsedInfoByServerInfoAndPortForUpdate(request.getOutIp(),
                request.getProxyIp(), request.getProxyPort());
        if (portUsed != null && portUsed.getChannelNum() > 1) {
            portUsedMapper.updateChannelNumById(portUsed.getId(), portUsed.getChannelNum() - 1);
        } else if (portUsed != null) {
            portUsedMapper.removePortUsedById(portUsed.getId());
        }
        return ResponseUtil.createResponse(ResponseCodeEnum.SUCCESS, CloseClientChannelResponse.class);
    }

    /**
     * 注册代理服务器
     * 向proxy_tserver表新增记录
     * */
    @Transactional
    @Log
    public ServerRegisterResponse serverRegister(ServerRegisterRequest request) {
        Server server = Server.builder()
                .id(IdUtil.snowId())
                .outIp(request.getOutIp())
                .ip(request.getIp()).build();
        serverMapper.insert(server);
        return ResponseUtil.createResponse(ResponseCodeEnum.SUCCESS, ServerRegisterResponse.class);
    }

    /**
     * 注销代理服务器
     * 删除proxy_tserver和proxy_tportused表的相关记录
     * */
    @Transactional
    @Log
    public ServerUnregisterResponse serverUnregister(ServerUnregisterRequest request) {
        Server server = serverMapper.queryServerByOutIpAndIp(request.getOutIp(), request.getIp());
        if (server != null) {
            portUsedMapper.removePortUsedByServerId(server.getId());
            serverMapper.removeById(server.getId());
        }
        return ResponseUtil.createResponse(ResponseCodeEnum.SUCCESS, ServerUnregisterResponse.class);
    }
}
